This library supports communication of commands between machines, including broadcast commands that are sent to several machines at once. Unfortunately, machines must be modified to support this library. However, these changes are relatively simple.
#include "../Ninereeds Broadcast Lib/Ninereeds Broadcast Lib.h"
class mi : public CMachineInterface, public c_Broadcast_Listener_CB { ... };
virtual void Transpose_Notes (int p_Semitones); virtual void All_Notes_Off (void); virtual void All_Notes_Ignore (void); virtual void Set_Control (int p_ID, int p_Value); virtual void Tick2 (void); virtual void Tick2 (int p_Channel, c_Broadcast_Params *p_Command); virtual bool Work2 (float *psamples, int numsamples, int const mode, int offset);
Set_Master_Info (pMasterInfo); NR_Start_Listening (0, this);
NR_Stop_Listening (0, this);
void mi::Tick() { Handle_Tick (); } void mi::Tick2 (void) { // Code from the original Tick function } void mi::Tick2 (int p_Channel, c_Broadcast_Params *p_Command) { // Code to handle received broadcast commands } bool mi::Work (float *psamples, int numsamples, int const mode) { return Handle_Work (psamples, numsamples, mode); } bool mi::Work2(float *psamples, int numsamples, int const mode, int offset) { // Code from original Work function }Note that there may be slight deviations from this - for example, to receive an AuxBus signal it is better to fetch the data in mi::Work, and update a pointer to the relevant part in mi::Work2.
void mi::Transpose_Notes (int p_Semitones) { int l_Note; for (int i = 0; i < MAX_TRACKS; i++) { if ( (tval [i].noteinit != NOTE_NO ) && (tval [i].noteinit != NOTE_OFF)) { l_Note = ((tval [i].noteinit >> 4) * 12) + (tval [i].noteinit & 0x0F); l_Note += p_Semitones; if ((l_Note >= NOTE_MIN) && (l_Note <= NOTE_MAX)) { tval [i].noteinit = ((l_Note / 12) << 4) + (l_Note % 12); } else { tval [i].noteinit = NOTE_OFF; } } if ( (tval [i].note != NOTE_NO ) && (tval [i].note != NOTE_OFF)) { l_Note = ((tval [i].note >> 4) * 12) + (tval [i].note & 0x0F); l_Note += p_Semitones; if ((l_Note >= NOTE_MIN) && (l_Note <= NOTE_MAX)) { tval [i].note = ((l_Note / 12) << 4) + (l_Note % 12); } else { tval [i].noteinit = NOTE_OFF; tval [i].note = NOTE_NO; } } } } void mi::All_Notes_Off (void) { for (int i = 0; i < MAX_TRACKS; i++) { tval [i].noteinit = NOTE_OFF; } } void mi::All_Notes_Ignore (void) { for (int i = 0; i < MAX_TRACKS; i++) { if ( (tval [i].noteinit != NOTE_NO ) && (tval [i].noteinit != NOTE_OFF)) { tval [i].noteinit = NOTE_NO; } tval [i].note = NOTE_NO; } } void mi::Set_Control (int p_ID, int p_Value) { switch (p_ID) { case 0 : // May make a convention that this is always volume? { // On the other hand, volume is better handled by fade controls for (int i = 0; i < MAX_TRACKS; i++) { tval [i].volume = p_Value >> 8; } break; } } }
Important - if any of the callbacks above are not to be handled, they must still by defined. In this case, provide the functions as above but do not include the code.
The reason that there are no default functions for this is to ensure that exclusions are a decision rather than through forgetting.
The easiest way to achieve this is to use the dialog that is built into the broadcast library DLL. This also supports configuration of a machines controls. To do this...
CMachineInfo const MacInfo = { MT_GENERATOR, MI_VERSION, 0, // type, version, flags 1, MAX_TRACKS, // min, max tracks 0, 3, pParameters, // num globalpars, num trackpars, *pars 0, NULL, // num attribs, *attribs "Ninereeds NRS04", "NRS04", "Steve Horne", // name, short name, author "Edit Ninereeds NRS04" // command menu };
virtual void Command (int const i); virtual void Save (CMachineDataOutput * const po);
bool f_Listen_Enabled; int f_Listen_Channel;
#define NUM_CONTROL_CONFIGS 6 c_Control_Config g_Control_Configs [NUM_CONTROL_CONFIGS] = { {0, "Volume" }, {1, "Attack" }, {2, "Decay" }, {3, "Bend Rate" }, {4, "Fractal Effect Low" }, {5, "Fractal Effect High"} }; void mi::Command(int const i) { switch (i) { case 0 : // Edit Ninereeds Softy { NR_Dialog_Enable_Channel (this, &f_Listen_Enable, &f_Listen_Channel, NUM_CONTROL_CONFIGS, g_Control_Configs ); } break; } }
Do remember when implementing mi::Init to check if pi is NULL before trying to read data back - pi will be NULL for any buzz files that did not have machine specific data recorded.
Finally, the functions to load and save the control configurations are...
Read_Controls (pi); Write_Controls (po);
Controls are very easy to set up - just add an item to your control list, and add the implementation to the Set_Control method. It is possible to use them for things you don't have parameters for, but really its just a way of controlling machine parameters externally with the added bonus of automatic control slides.
In particular, controls have no more precision of time or value than is available to normal parameters.
Machine specific commands require significantly more work, but are not subject to these limitations.